/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package asis;

import asis.ini.INI;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import skyproc.*;
import skyproc.exceptions.NotFound;
import skyproc.gui.SPProgressBarPlug;

/**
 *
 * @author pc tech
 */
public class SpawnRandomizer {

    public void addRandomSpawns() {
        internalAddRandomSpawns();
    }

    public void addRandomSpawns(ASIS a) {
        if (a.getModExclusions() != null) {
            modExclusions.addAll(a.getModExclusions());
        }
        internalAddRandomSpawns();
    }

    private void internalAddRandomSpawns() {
        SPProgressBarPlug.setStatus("Spawn Randomizer: Starting Patch");
        SPProgressBarPlug.incrementBar();
        Mod merger = new Mod("TmpMerger", false);
        merger.addAsOverrides(SPGlobal.getDB());
        //Processes the ini files and checks them for the patch settings.
        processINI();
        //getPatchSettings();
        getBlockList();
        getLeveledLists(merger);
        getRandomSpawnSettings(merger);
        getRandomSpawnLists();
        addRandomSpawnsToNPCs(merger);
    }

    private void addRandomSpawnsToNPCs(Mod merger) {

        String scriptName = "randomizedSpawner";
        int num;
        float numRed;
        float numInc;

        FLST listOfValidCells = null;
        FormID uniqueFaction = null;
        FormID uniqueFaction2 = null;

        try {
            listOfValidCells = new FLST(SPGlobal.getGlobalPatch(), "listOfValidCells");
            uniqueFaction = new FormID("000802", SPGlobal.getDB().getMod(new ModListing("ASIS-Dependency", false)).getInfo());
            uniqueFaction2 = new FormID("000800", SPGlobal.getDB().getMod(new ModListing("ASIS-Dependency", false)).getInfo());
        } catch (NullPointerException exception) {
            JOptionPane.showMessageDialog(null, "The patcher will not work correctly if ASIS-Dependency.esp is not\ninstalled before running the patcher.  Please install the plugin\n(and check that it has not been ghosted by Wrye Bash) and try\nagain.  If you have done so and continue to see this message,\nplease contact the author.  The patcher will now exit.");
            System.exit(0);
        }

        SPProgressBarPlug.setStatus("Spawn Randomizer: Adding Random Spawn Scripts (may take several minutes)");
        SPProgressBarPlug.incrementBar();

        for (int i = 0; i < cellExclusions.size(); i++) {
            listOfValidCells.addFormEntry((FormID) cellExclusions.get(i));
        }

        for (NPC_ n : merger.getNPCs()) {
            if (validActors.contains(n.getEDID())) {
                try {
                    //Determines any unique NPC maximum spawn settings.
                    num = getNumMaxSpawns(n);
                    numRed = getNumReducedSpawns(n);
                    numInc = getNumIncreasedSpawns(n);
                    //Actual script addition is done here.
                    ScriptRef script = new ScriptRef(scriptName);
                    //script.setProperty("creatureToSpawn", dupNPCForm);
                    script.setProperty("listOfValidCells", listOfValidCells.getForm());
                    script.setProperty("listOfNPCs", spawnAssignmentsFLST.get(n).getForm());
                    script.setProperty("uniqueSpawnFaction", uniqueFaction);
                    script.setProperty("uniqueSpawnFaction2", uniqueFaction2);
                    //script.setProperty("creatureToSpawn", n.getForm());
                    script.setProperty("useInteriorSpawns", useInteriorSpawns);
                    if (n.getEDID().toUpperCase().contains("AMBUSH")) {
                        script.setProperty("isAmbushSpawn", true);
                    } else {
                        script.setProperty("isAmbushSpawn", false);
                    }
                    script.setProperty("numMaxSpawns", num);
                    script.setProperty("randomSpawnChance", randomSpawnChance);
                    script.setProperty("multiGroupChance", multiGroupChance);
                    script.setProperty("numMaxGroups", numMaxGroups);
                    if (Util.isValid(numRed) && Util.isValid(numInc)) {
                        for (int i = 0; i < 10; i++) {
                            script.setProperty("spawn" + i + "weight", (spawnweight[i] / numRed) * numInc);
                        }
                    } else if (Util.isValid(numRed) && !Util.isValid(numInc)) {
                        for (int i = 0; i < 10; i++) {
                            script.setProperty("spawn" + i + "weight", (spawnweight[i] / numRed));
                        }
                    } else if (!Util.isValid(numRed) && Util.isValid(numInc)) {
                        for (int i = 0; i < 10; i++) {
                            script.setProperty("spawn" + i + "weight", (spawnweight[i] * numInc));
                        }
                    } else {
                        for (int i = 0; i < 10; i++) {
                            script.setProperty("spawn" + i + "weight", spawnweight[i]);
                        }
                    }
                    n.scripts.addScript(script);
                    SPGlobal.getGlobalPatch().addRecord(n);
                } catch (Exception e) {
                    SPGlobal.logException(e);
                }
            }
        }

        //Clean-up NPC's from excluded mods.  This is done as blocking the import 
        // could cause previous NPC's to override an excluded mods NPC's.
        String list;
        Mod tempMod;
        GRUP patchGRUP = SPGlobal.getGlobalPatch().getNPCs();
        for (int i = 0; i < modExclusions.size(); i++) {
            list = (String) modExclusions.get(i);
            try {
                tempMod = SPGlobal.getDB().getMod(new ModListing(list));
                for (NPC_ n : tempMod.getNPCs()) {
                    patchGRUP.removeRecord(n.getForm());
                }
            } catch (Exception e) {
                //Do nothing, probably doesn't have the mod in the exclusions.
            }
        }
    }

    private void processINI() {

        SPProgressBarPlug.setStatus("Spawn Randomizer: Processing INI");
        SPProgressBarPlug.incrementBar();
        INI ini = null;

        //Sets up the file reader for the ini file.
        try {
            ini = new INI("RandomSpawns.ini");


        } catch (IOException ex) {
            Logger.getLogger(NPCPotions.class.getName()).log(Level.SEVERE, null, ex);
        }

        Collection<INI.IniSectionHead> sections = getSectionList();

        ini.addSection(sections);

        try {
            ini.readData();


        } catch (IOException ex) {
            Logger.getLogger(NPCPotions.class.getName()).log(Level.SEVERE, null, ex);
        }

        initializeLists(ini);
    }

    private boolean getValidNPC(NPC_ n) {
        boolean include = false;
        boolean exclude = false;
        String list;
        String edid = n.getEDID().toUpperCase();

        if (!npcBlockList.contains(n.getForm())) {
            if (inclusions.isEmpty()) {
                include = true;
            }
            if (!include) {
                for (int i = 0; i < inclusions.size(); i++) {
                    list = ((String) inclusions.get(i)).toUpperCase();
                    if (edid.contains(list)) {
                        include = true;
                        break;
                    }
                }
            }
            if (include && !exclusions.isEmpty() && !modExclusions.isEmpty() && !exclusionsStartsWith.isEmpty()) {
                exclude = false;
            }
            if (!exclude) {
                for (int i = 0; i < exclusions.size(); i++) {
                    list = ((String) exclusions.get(i)).toUpperCase();
                    if (edid.contains(list)) {
                        exclude = true;
                        break;
                    }
                }
                for (int i = 0; i < exclusionsStartsWith.size(); i++) {
                    list = ((String) exclusionsStartsWith.get(i)).toUpperCase();
                    if (edid.startsWith(list)) {
                        exclude = true;
                        break;
                    }
                }
            }
            if (include && !exclude) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    private void getRandomSpawnLists() {

        SPProgressBarPlug.setStatus("Spawn Randomizer: Generating Random Spawn Settings (may take several minutes or more)");
        SPProgressBarPlug.incrementBar();

        FLST temp;
        ArrayList<Integer> tempGroups;
        int j;

        for (RandomActor a : randomActors) {
            temp = new FLST(SPGlobal.getGlobalPatch(), a.getFormStr());
            tempGroups = a.getGroupList();
            for (int i = 0; i < tempGroups.size(); i++) {
                j = tempGroups.get(i);
                temp.addFormEntry(assignments.get(j).getForm());
            }
            spawnAssignmentsFLST.put(a.getNPC(), temp);
        }
    }

    private void getRandomSpawnSettings(Mod merger) {

        SPProgressBarPlug.setStatus("Spawn Randomizer: Generating Random Spawn Settings (may take several minutes or more)");
        SPProgressBarPlug.incrementBar();

        Scanner s;
        String key;
        ArrayList<Integer> value;

        for (NPC_ n : merger.getNPCs()) {
            for (Map.Entry<String, ArrayList<Integer>> entry : spawnAssignments.entrySet()) {
                key = entry.getKey();
                value = entry.getValue();
                if (Util.containsIgnoreCase(n.getEDID(), key) && getValidNPC(n)) {
                    randomActors.add(new RandomActor(n, value));
                    validActors.add(n.getEDID());
                }
            }
        }
    }

    private void getLeveledLists(Mod merger) {
        int key;
        String value;
        Scanner s;
        LVLN li;

        for (NPC_ n : merger.getNPCs()) {
            for (Map.Entry<Integer, String> entry : assignmentsStr.entrySet()) {
                key = entry.getKey();
                value = entry.getValue();
                s = new Scanner(value);
                s.useDelimiter(" ");
                while (s.hasNext()) {
                    if (Util.containsIgnoreCase(n.getEDID(), s.next()) && getValidNPC(n)) {
                        li = assignments.get(key);
                        try {
                            li.addEntry(n.getForm(), 1, 1);
                        } catch (NotFound ex) {
                            Logger.getLogger(SpawnRandomizer.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                }
                s.close();
            }
        }
    }

    private int getNumMaxSpawns(NPC_ n) {
        String list;
        if (!npcMaxSettings.isEmpty()) {
            for (Map.Entry<String, String> entry : npcMaxSettings.entrySet()) {
                list = entry.getKey();
                if (n.getEDID().toUpperCase().contains(list)) {
                    return Integer.parseInt(npcMaxSettings.get(list));
                }
            }
        }
        return 9;
    }

    private float getNumReducedSpawns(NPC_ n) {
        String list;
        if (!npcReducedSettings.isEmpty()) {
            for (Map.Entry<String, String> entry : npcReducedSettings.entrySet()) {
                list = entry.getKey();
                if (n.getEDID().toUpperCase().contains(list)) {
                    return Float.parseFloat(npcReducedSettings.get(list));
                }
            }
        }
        return -1;
    }

    private float getNumIncreasedSpawns(NPC_ n) {
        String list;
        if (!npcIncreasedSettings.isEmpty()) {
            for (Map.Entry<String, String> entry : npcIncreasedSettings.entrySet()) {
                list = entry.getKey();
                if (n.getEDID().toUpperCase().contains(list)) {
                    return Float.parseFloat(npcIncreasedSettings.get(list));
                }
            }
        }
        return -1;
    }

    private void getBlockList() {
        //Clean-up NPC's from excluded mods.  This is done as blocking the import 
        // could cause previous NPC's to override an excluded mods NPC's.
        String list;
        Mod tempMod;
        for (int i = 0; i < modExclusions.size(); i++) {
            list = (String) modExclusions.get(i);
            try {
                tempMod = SPGlobal.getDB().getMod(new ModListing(list));
                for (NPC_ n : tempMod.getNPCs()) {
                    npcBlockList.add(n.getForm());
                }
            } catch (Exception e) {
                //Do nothing, probably doesn't have the mod in the exclusions.
            }
        }
    }

    private Collection<INI.IniSectionHead> getSectionList() {
        Collection<INI.IniSectionHead> sectionList = new ArrayList<>();

        for (IniSection currentSection : IniSection.values()) {
            sectionList.add(new INI.IniSectionHead(currentSection.getName(), currentSection.getFormat()));
        }

        return sectionList;
    }

    private void initializeLists(INI ini) {
        inclusions = (ArrayList) ini.getCollection(new INI.IniSectionHead(IniSection.NPCINCLUSIONS.getName(),
                IniSection.NPCINCLUSIONS.getFormat()));
        exclusionsStartsWith = (ArrayList) ini.getCollection(new INI.IniSectionHead(IniSection.NPCEXCLUSIONSSTARTSWITH.getName(),
                IniSection.NPCEXCLUSIONSSTARTSWITH.getFormat()));
        exclusions = (ArrayList) ini.getCollection(new INI.IniSectionHead(IniSection.NPCEXCLUSIONS.getName(),
                IniSection.NPCEXCLUSIONS.getFormat()));
        modExclusions = (ArrayList) ini.getCollection(new INI.IniSectionHead(IniSection.MODEXCLUSIONS.getName(),
                IniSection.MODEXCLUSIONS.getFormat()));

        patchSettingsMap = ini.getMap(new INI.IniSectionHead(IniSection.PATCHSETTINGS.getName(),
                IniSection.PATCHSETTINGS.getFormat()));

        cellExclusions = (ArrayList) ini.getCollectionForms(new INI.IniSectionHead(IniSection.MODEXCLUSIONS.getName(),
                IniSection.MODEXCLUSIONS.getFormat()));

        npcMaxSettings = ini.getMap(new INI.IniSectionHead(IniSection.NPCMAXSPAWNSETTINGS.getName(),
                IniSection.NPCMAXSPAWNSETTINGS.getFormat()));

        npcReducedSettings = ini.getMap(new INI.IniSectionHead(IniSection.NPCREDUCEDSPAWNSETTINGS.getName(),
                IniSection.NPCREDUCEDSPAWNSETTINGS.getFormat()));

        spawnAssignments = ini.getMapStrIntArrayList(new INI.IniSectionHead(IniSection.SPAWNASSIGNMENTS.getName(),
                IniSection.SPAWNASSIGNMENTS.getFormat()));

        assignmentsStr = ini.getMapIntStr(new INI.IniSectionHead(IniSection.SPAWNNUMBERS.getName(),
                IniSection.SPAWNNUMBERS.getFormat()));

        LVLN li;
        for (Map.Entry<Integer, String> entry : assignmentsStr.entrySet()) {
            li = new LVLN(SPGlobal.getGlobalPatch(), "LVLN" + entry.getKey());
            li.set(LeveledRecord.LVLFlag.CalcAllLevelsEqualOrBelowPC, true);
            li.set(LeveledRecord.LVLFlag.CalcForEachItemInCount, true);
            assignments.put(entry.getKey(), li);
        }

        spawnweight[0] = ASIS.save.getInt(ASISSaveFile.GUISettings.SRSPAWN0WEIGHT);
        spawnweight[1] = ASIS.save.getInt(ASISSaveFile.GUISettings.SRSPAWN1WEIGHT);
        spawnweight[2] = ASIS.save.getInt(ASISSaveFile.GUISettings.SRSPAWN2WEIGHT);
        spawnweight[3] = ASIS.save.getInt(ASISSaveFile.GUISettings.SRSPAWN3WEIGHT);
        spawnweight[4] = ASIS.save.getInt(ASISSaveFile.GUISettings.SRSPAWN4WEIGHT);
        spawnweight[5] = ASIS.save.getInt(ASISSaveFile.GUISettings.SRSPAWN5WEIGHT);
        spawnweight[6] = ASIS.save.getInt(ASISSaveFile.GUISettings.SRSPAWN6WEIGHT);
        spawnweight[7] = ASIS.save.getInt(ASISSaveFile.GUISettings.SRSPAWN7WEIGHT);
        spawnweight[8] = ASIS.save.getInt(ASISSaveFile.GUISettings.SRSPAWN8WEIGHT);
        spawnweight[9] = ASIS.save.getInt(ASISSaveFile.GUISettings.SRSPAWN9WEIGHT);
        useInteriorSpawns = ASIS.save.getBool(ASISSaveFile.GUISettings.SRUSEINTERIORSPAWNS);
        randomSpawnChance = ASIS.save.getInt(ASISSaveFile.GUISettings.SRRANDOMSPAWNCHANCE);
        multiGroupChance = ASIS.save.getInt(ASISSaveFile.GUISettings.SRMULTIGROUPCHANCE);
        numMaxGroups = ASIS.save.getInt(ASISSaveFile.GUISettings.SRNUMMAXGROUPS);
    }

    private enum IniSection {

        PATCHSETTINGS(INI.IniDataFormat.KEY_VALUE, "PATCHSETTINGS"),
        NPCINCLUSIONS(INI.IniDataFormat.VALUE, "NPCINCLUSIONS"),
        NPCEXCLUSIONS(INI.IniDataFormat.VALUE, "NPCEXCLUSIONS"),
        NPCEXCLUSIONSSTARTSWITH(INI.IniDataFormat.VALUE, "NPCEXCLUSIONSSTARTSWITH"),
        NPCMAXSPAWNSETTINGS(INI.IniDataFormat.KEY_VALUE, "NPCMAXSPAWNSETTINGS"),
        NPCREDUCEDSPAWNSETTINGS(INI.IniDataFormat.KEY_VALUE, "NPCREDUCEDSPAWNSETTINGS"),
        CELLEXCLUSIONS(INI.IniDataFormat.VALUE, "CELLEXCLUSIONS"),
        MODEXCLUSIONS(INI.IniDataFormat.VALUE, "MODEXCLUSIONS"),
        SPAWNASSIGNMENTS(INI.IniDataFormat.KEY_VALUE, "SPAWNASSIGNMENTS"),
        SPAWNNUMBERS(INI.IniDataFormat.KEY_VALUE, "SPAWNNUMBERS");
        private INI.IniDataFormat format;
        private String name;

        IniSection(INI.IniDataFormat format, String name) {
            this.format = format;
            this.name = name;
        }

        String getName() {
            return name;
        }

        INI.IniDataFormat getFormat() {
            return format;
        }
    }
    String npcType = null;
    String npcSpawnCount = null;
    String settingType = null;
    String settingValue = null;
    Map<String, String> npcMaxSettings = new HashMap<>();
    Map<String, String> npcReducedSettings = new HashMap<>();
    Map<String, String> npcIncreasedSettings = new HashMap<>();
    Map<String, String> patchSettingsMap = new HashMap<>();
    Map<String, ArrayList<Integer>> spawnAssignments = new HashMap<>();
    Map<NPC_, FLST> spawnAssignmentsFLST = new HashMap<>();
    Map<FormID, FormID> formsOrigDup = new HashMap<>();
    ArrayList inclusions = new ArrayList<>(0);
    ArrayList exclusionsStartsWith = new ArrayList<>(0);
    ArrayList exclusions = new ArrayList<>(0);
    ArrayList modExclusions = new ArrayList<>(0);
    ArrayList<FormID> npcBlockList = new ArrayList<>();
    ArrayList cellExclusions = new ArrayList<>(0);
    ArrayList<RandomActor> randomActors = new ArrayList<>(0);
    ArrayList<String> validActors = new ArrayList<>(0);
    Map<Integer, String> assignmentsStr = new HashMap<>();
    Map<Integer, LVLN> assignments = new HashMap<>();
    float[] spawnweight = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    boolean useInteriorSpawns = false;
    int randomSpawnChance = 0;
    int multiGroupChance = 0;
    int numMaxGroups = 0;
    public int numProgressSections = 5;
}
